/* IP.java */

package SSF.OS;

import java.util.*;

import com.renesys.raceway.DML.*;

import SSF.Net.*;
import SSF.Net.Util.*;

import com.renesys.raceway.SSF.*;


/**
  * This class implements a subset of the IP protocol. The primary job
  * of IP is to find a route for each datagram and send it on its way.
  * In order to allow gateways or other intermediate systems to forward the
  * datagram, it adds its own header (IpHeader.java).  This version
  * of IP uses a 'next hop' IP address as a surrogate hardware address; 
  * future alternatives with more elaborate link layer models will fix this.
  * <pre>
  * Revisions ato 9/24/00: support for configuration of Monitors.
  * Revisions jhc 1/29/01: support for ICMP messages before drop()
  * </pre>
  * @see SSF.OS.IpHeader
  */
public class IP extends ProtocolSession implements Configurable {
  /** An array of NIC(s) controlled by this IP. */
  public Vector INTERFACES;

  /** The IP routing table. */
  protected RoutingTable ROUTING_TABLE;

  /** Return the IP routing table. */
  public RoutingTable getRoutingTable() {
    return ROUTING_TABLE;
  };

  protected demux_cache session_cache;

  public final int INTERFACE_SET_STARTING_SIZE = 2;
  public NIC[] INTERFACE_SET;
  public int INTERFACE_COUNT;

  private boolean DEBUG  = false;

  protected String tieBreakerClass = null;

  // Class implementing IpMonitor used to monitor and/or mark IP packets
  private ProtocolMonitor monitor = null;

  // The ProtocolMonitor on/off switch */
  private boolean monitorON = false;
  
  // The ttl drop of a packet. Added by Tamir Carmeli
  private int ttlDrop = 1;
  
  // TTL drop changes from this time. Added by Tamir Carmeli
  Double ttlChangeFrom;

  // TTL drop changes until this time. Added by Tamir Carmeli
  Double ttlChangeUntil;

  // The ttl drop of a packet during the change interval. Added by Tamir Carmeli
  Integer ttlChangeValue;
  

//------------------------------------------------------ CONSTRUCTOR

  public IP() {
    super();
    INTERFACES = new Vector();
    session_cache = new demux_cache();
    INTERFACE_SET  = new NIC[INTERFACE_SET_STARTING_SIZE];
    INTERFACE_COUNT = 0;
  }

  
  public void setGraph(ProtocolGraph G) throws ProtocolException { 
    RouteTieBreaker T = null;
    try {
	T = (tieBreakerClass==null?null:(RouteTieBreaker)
	 (Class.forName(tieBreakerClass).newInstance()));
    } catch (Exception any) {
        System.out.println(debugIdentifier() + any);
    }

    ROUTING_TABLE = new RadixTreeRoutingTable(G,T);
    super.setGraph(G);
  }

//------------------------------------------------------ CONFIGURATION

  /** Configure this IP session. Example of the supported DML attributes:
   * <PRE>
   *   ProtocolSession [for ip use SSF.OS.IP
   *     debug   %S    # print verbose diagnostics, true/false
   *
   *     tiebreaker [
   *       use   %S    # class name to use to choose routes of equal cost
   *     ]
   *
   *     monitor [
   *       use   %S    # class name to use for IP Monitoring
   *     ]
   *   ]
   * </PRE>
   * If <TT>tiebreaker</TT> is omitted, the default is a simple hash on src and
   * dest address provided in an anonymous inner class in SSF.Net.RadixTreeRoutingTable.
   *
   */
  public void config(Configuration cfg) throws configException {
    super.config(cfg);
    String str;

    str = (String)cfg.findSingle("debug");
    if (str != null)
      DEBUG = Boolean.valueOf(str).booleanValue();

    Configuration mConfig = (Configuration)cfg.findSingle("monitor");
    if (mConfig != null) createMonitor(mConfig);
	
    str = (String)cfg.findSingle("tiebreaker.use");
    if (str != null) tieBreakerClass =  str;
    
    // Added by Tamir Carmeli
    str = (String)cfg.findSingle("ttl_penalty");
    if (null != str)
    {
    	ttlDrop = new Integer(str).intValue();
    }
    
    String ttlChangeFromStr = (String)cfg.findSingle("ttl_penalty_change.from");
    String ttlChangeUntilStr = (String)cfg.findSingle("ttl_penalty_change.until");
    String ttlChangeValueStr = (String)cfg.findSingle("ttl_penalty_change.value");
    
    if (null != ttlChangeFromStr || null != ttlChangeUntilStr || null != ttlChangeValueStr)
    {

        if (null == ttlChangeFromStr || null == ttlChangeUntilStr || null == ttlChangeValueStr )
        {
        	throw new configException("Defined only one of ttl_penalty_change values"+this);
        }
        
        ttlChangeFrom = new Double(ttlChangeFromStr);
        ttlChangeUntil = new Double(ttlChangeUntilStr);
    	
    	if (ttlChangeUntil < ttlChangeFrom)
    	{
    		throw new configException("TTL change until smaller than TTL change from! "+this);
    	}
    	
    	ttlChangeValue = new Integer(ttlChangeValueStr);
    }
      }

  /** Instantiate and configure an IP packet monitor implementing
   *  the interface SSF.OS.ProtocolMonitor, if specified
   *  locally in this host's IP configuration.
   */
  private void createMonitor(Configuration config) throws configException {

    String monitor_type = (String)config.findSingle("use");
	
    if (monitor_type == null)
      throw new configException("\n"+debugIdentifier()+" DML monitor.use is not specified");

    try {
      Class mClass = Class.forName(monitor_type);
      Object mobj = mClass.newInstance();
      monitor = (ProtocolMonitor)mobj;
    }
    catch (Exception any) {
      System.err.println(debugIdentifier()+" Can't create an instance of "+monitor_type);
      any.printStackTrace();
      throw new configException("Can't create monitor "+monitor_type);
    }

    //let the monitor do its own config, and let it know the owner IP session
    monitor.config(this, config);

    //turn on the monitor
    setMonitorEnable(true);
  }

  /** Monitor may need to initialize after the config() phase. */
  public void init() throws ProtocolException {
    if (monitor != null) monitor.init();
  }

  /** An ProtocolMonitor may turn on and off calls to its receive()
   *  method. NOTE: the ProtocolMonitor MUST explicitely set
   *  enableMonitor(true) in its init() method to begin
   *  receiving IP packets.
   */
   public void setMonitorEnable(boolean en) {
     if(monitor != null) monitorON = en;
   }

  /** An ProtocolMonitor may inquire if IP calls to its receive() are enabled */
  public boolean getMonitorEnable() {
    return monitorON;
  }

//------------------------------------------------------- OPENED NIC

  public void opened(ProtocolSession nic) throws ProtocolException {
    INTERFACES.addElement(nic); // DEPRECATED

    if (INTERFACE_COUNT == INTERFACE_SET.length) {  // must expand
      NIC[] larger = new NIC[INTERFACE_SET.length*2];
      System.arraycopy(INTERFACE_SET,0,larger,0,INTERFACE_SET.length);
      // :DELETE: INTERFACE_SET
      INTERFACE_SET = larger;
    }
    INTERFACE_SET[INTERFACE_COUNT++] = (NIC)nic;
  }


//------------------------------------------------------- PACKET HANDLING

  /** demux_cache: class internal to IP, used for rapid demultiplexing
    * of protocol numbers to protocol session instances.  Two dynamic
    * arrays are maintained: a list of protocol numbers and a list of 
    * protocol sessions.  These lists are unordered, but the most recently
    * requested protocol number may always be found in slot 0.  
    */
  protected final class demux_cache {
    int curr, max;
    int[] numbers;
    ProtocolSession[] sessions;
    final int INITIAL_CACHE_SIZE = 4;

    public demux_cache() {
      curr = 0; // will initialize on first use
    }

    public final ProtocolSession demux(int pnum) throws ProtocolException {
      if (curr == 0) {
	max = INITIAL_CACHE_SIZE; 
	sessions = new ProtocolSession[max];
	numbers  = new int[max];
	numbers[0] = pnum;
	try {
	  sessions[0] =
           IP.this.inGraph.SessionForName(Protocols.getProtocolByNumber(pnum));
	} catch (ProtocolException pex) {
	  System.err.println(debugIdentifier() + pex);
	  sessions[0] = null;
	}
	curr = 1;
      }

      if (numbers[0] == pnum)  // Common case: most recently requested. 
	return sessions[0];

      else for (int n=1; n<curr; n++) 
	if (numbers[n]==pnum) {  // Found it; swap it into slot 0.
	  int tmp = numbers[0];
	  numbers[0]=numbers[n];
	  numbers[n]=tmp;
	  ProtocolSession tmps = sessions[0];
	  sessions[0]=sessions[n];
	  sessions[n]=tmps;
	  return sessions[0];
	}

      // Not found in cache. 

      if (curr+1 == max) { 
	// out of room; expand the arrays first 
	ProtocolSession[] larger_sessions = new ProtocolSession[2*max];
	int[] larger_numbers = new int[2*max];
	System.arraycopy(sessions,0,larger_sessions,0,max);
	System.arraycopy(numbers,0,larger_numbers,0,max);
	max *= 2;
	// :DELETE: sessions
	// :DELETE: numbers 
	sessions = larger_sessions;
	numbers = larger_numbers;
      }
      numbers[curr] = numbers[0];
      sessions[curr] = sessions[0];
      numbers[0] = pnum;
      try {
	sessions[0] = IP.this.inGraph.SessionForName
	  (Protocols.getProtocolByNumber(pnum));
      } catch (ProtocolException pex) {
	System.err.println(debugIdentifier() + pex);
	sessions[0] = null;
      }
      curr++;
      return sessions[0];
    }
  }

      
  public boolean push(ProtocolMessage message, ProtocolSession fromSession)
                                                     throws ProtocolException {
    IpHeader ipHeader = (IpHeader) message;

    // 1. If the local host is the destination indicated by the ipHeader,
    //    push the payload up to the correct protocol.

    boolean isLocal = // loopback address 127.0.0.1 with netmask 255.0.0.0
	(0x7F000000 == (0xFF000000 & ipHeader.DEST_IP)); 

    for (int n=0; !isLocal && n<INTERFACE_COUNT; n++) 
      isLocal = (INTERFACE_SET[n].ipAddr == ipHeader.DEST_IP);
    
    if (isLocal) { 
      ProtocolSession handler = session_cache.demux(ipHeader.PROTOCOL_NO);
      if (handler != null) {
        if (monitorON)
          monitor.receive(ipHeader, fromSession, handler);
	return (handler.push(ipHeader.payload(), this));
      } else {
	if (!ICMP.suppressReporting(ipHeader))
	    push(ICMPHeader.makeProtocolUnreachableMessage(ipHeader).
		 ipHeader(),this);
        drop(ipHeader);
        return false;
      }
    }

    // Changed by Tamir Carmeli
    boolean isTimeToLiveOver = (0 >= ipHeader.TIME_TO_LIVE);
  
    int effectiveTTLDrop = ttlDrop;
    
	if (ttlChangeFrom != null && ttlChangeUntil != null && ttlChangeValue != null)
	{
		if ((entity.now() / Net.frequency) < ttlChangeUntil && ttlChangeFrom <= (entity.now() / Net.frequency))
		{
			effectiveTTLDrop = ttlChangeValue;  
		}
	}
	
    ipHeader.TIME_TO_LIVE-= effectiveTTLDrop;

    // 2. If the message has reached the end of time-to-live, drop it.
    // if (0 >= --ipHeader.TIME_TO_LIVE) {  bugfix 12-13-01    
    if (isTimeToLiveOver) {
      if (!ICMP.suppressReporting(ipHeader))
	  push(ICMPHeader.makeTimeExceededMessage(ipHeader).ipHeader(),this);
      drop(ipHeader);
      return false;
    }
      
    // 3. If this host is not the destination of this ipHeader, consult 
    //    the forwarding table and send it on the proper network interface.
    RoutingInfo rtgInfo = 
	ROUTING_TABLE.findBest(ipHeader.SOURCE_IP,ipHeader.DEST_IP);
      
    int recurse = 0;
    while (rtgInfo != null && rtgInfo.next_hop_interface() == null) {
      recurse++;
      if (recurse > 10) { // recursive lookup limit
        throw new Error("IP: recursive lookup limit exceeded");
      }
      rtgInfo=ROUTING_TABLE.findBest(ipHeader.SOURCE_IP,rtgInfo.next_hop_ip());
    }

    if (rtgInfo != null) {
      NIC use_iface = rtgInfo.next_hop_interface();

      // if no source is specified in the header, then set it to the
      // interface which it is about to be sent out on
      if (ipHeader.SOURCE_IP < 0) {
        if (DEBUG) {
          System.err.println(debugIdentifier() + "invalid IP source address: "
                             + ipHeader.SOURCE_IP);
        }
        ipHeader.SOURCE_IP = use_iface.ipAddr;
      }

      ipHeader.NEXT_HOP_IP = rtgInfo.next_hop_ip();

      if (monitorON)
        monitor.receive(ipHeader, fromSession, use_iface);

      return (use_iface.push(ipHeader,this));
    } else {
      if (DEBUG) {
        System.out.println(debugIdentifier() + "no route for "
                           + IP_s.IPtoString(ipHeader.DEST_IP) + " in:");
        ((RadixTreeRoutingTable)ROUTING_TABLE).print("  ");
      }
      // shouldn't drop() be called here? -bjp 2000.06.29
      if (!ICMP.suppressReporting(ipHeader))
	  push(ICMPHeader.makeHostUnreachableMessage(ipHeader).
	       ipHeader(),this);
      drop(ipHeader);
      return false;
    }
  } // end of push()

  /** Method called when IP drops a packet that has reached the end of its
   *   lifetime, or has no route to its destination.
   */
  public void drop(IpHeader pkt) {
    if (DEBUG) {
      //System.out.println(debugIdentifier() + "dropping packet to " +
      //                   IP_s.IPtoString(pkt.DEST_IP));
      System.out.println(debugIdentifier() + "ip dropping pkt: \nip header= "+pkt.toString()+
                         "\npayload= "+pkt.payload().toString());
    }
  }
}

/*=                                                                      =*/
/*=  Copyright (c) 1997--2000  SSF Research Network                      =*/
/*=                                                                      =*/
/*=  SSFNet is open source software, distributed under the GNU General   =*/
/*=  Public License.  See the file COPYING in the 'doc' subdirectory of  =*/
/*=  the SSFNet distribution, or http://www.fsf.org/copyleft/gpl.html    =*/
/*=                                                                      =*/
